#include "ImaTcpRequestMaker.h"
#include "ImaLogger.h"
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 
#include <errno.h>

extern ImaLogger* gpLogger;

//ImaTcpRequestMaker* ImaTcpRequestMaker::mpInstance = NULL;

ImaTcpRequestMaker::ImaTcpRequestMaker(char* pHost, int port) {
	LOG(debug, __FILE__, __LINE__, "ImaTcpRequestMaker::ImaTcpRequestMaker invoked");

	strcpy(mHostname, pHost);
	mPort = port;

	if(createConnection() != 0) {
		char log_msg[1024];
		sprintf(log_msg, "Failed to create connection with server at host %s and port %d", mHostname, mPort);
		LOG(error, __FILE__, __LINE__, log_msg);
		exit(1);	//TODO: Handle this using exception
	}

	char log_msg[1024];
	sprintf(log_msg, "Connection with server at host %s and port %d established", mHostname, mPort);
	LOG(info, __FILE__, __LINE__, log_msg);
}

ImaTcpRequestMaker::~ImaTcpRequestMaker() {
	LOG(debug, __FILE__, __LINE__, "ImaTcpRequestMaker::~ImaTcpRequestMaker called");
	shutdown(mSockFd, SHUT_RDWR);
	close(mSockFd);
}


//ImaTcpRequestMaker* ImaTcpRequestMaker::getInstance(char* pHost, int port) {
//	LOG(debug, __FILE__, __LINE__, " ImaTcpRequestMaker::getInstance invloked");
//
//	//TODO: secure this instance using DCLP in case of multithreaded 
//	if(mpInstance == NULL) {
//		LOG(debug, __FILE__, __LINE__, "mpInstance is NULL; creating new instance");
//		mpInstance = new ImaTcpRequestMaker(pHost, port);
//	}
//
//	return mpInstance;
//}

int ImaTcpRequestMaker::createConnection() {
	LOG(debug, __FILE__, __LINE__, "createConnection called");

	struct sockaddr_in serveraddr;
	struct hostent *pServer;
	
	/* socket: create the socket */
	mSockFd = socket(AF_INET, SOCK_STREAM, 0);
	if(mSockFd < 0) {
		LOG(error, __FILE__, __LINE__, "Failed to create socket");
		return -1;
	}
	  
	/* gethostbyname: get the server's DNS entry */
	pServer = gethostbyname(mHostname);
	if(pServer == NULL) {
		fprintf(stderr,"ERROR, no such host as %s\n", mHostname);
		shutdown(mSockFd, SHUT_RDWR);
		close(mSockFd);
		return -1;
	}

	/* build the server's Internet address */
	bzero((char *) &serveraddr, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	bcopy((char *)pServer->h_addr, (char *)&serveraddr.sin_addr.s_addr, pServer->h_length);
	serveraddr.sin_port = htons(mPort);

    /* connect: create a connection with the server */
	if(connect(mSockFd, (sockaddr*)&serveraddr, sizeof(serveraddr)) < 0) {
		shutdown(mSockFd, SHUT_RDWR);
		close(mSockFd);
		char log_msg[1024];
		sprintf(log_msg, "connect() failed; Error: %s", strerror(errno));
		LOG(error, __FILE__, __LINE__, log_msg);
		return -1;
	}

	return 0;
}

//TODO: this is just a makeshift code; do more error handling in this
int ImaTcpRequestMaker::makeRequest(const char* pRequestStr, std::string& responseStr) {
	char log_msg[2048];
	sprintf(log_msg, "makeRequest called with request: %s", pRequestStr);	
	LOG(debug, __FILE__, __LINE__, log_msg);

	short sizeOfData = strlen(pRequestStr);
	int n = write(mSockFd, &sizeOfData, sizeof(short));
	if(n < 0) {
		responseStr = std::string("Failed to write size of data to be sent to server");
		return -1;
	}
	
	/* send the message line to the server */
	n = write(mSockFd, pRequestStr, sizeOfData);
	if(n < 0) {
		sprintf(log_msg, "write failed; Error: %s", strerror(errno));
		LOG(error, __FILE__, __LINE__, log_msg);
		responseStr = std::string("Failed to send request data to the server");
		return -1;
	}
	
	LOG(debug, __FILE__, __LINE__, "Request message successfully sent to the server; waiting for response");

	JSONDATA* pJsonData = NULL;
	pJsonData = (JSONDATA*) malloc(sizeof(JSONDATA));
	memset(pJsonData, 0, sizeof(JSONDATA));
	
	if(pJsonData->nBytesRead < 2) {
		int size_of_data = read_size_of_receivable_data(mSockFd, pJsonData);
		if(size_of_data < 0) {
			shutdown(mSockFd, SHUT_RDWR);
			close (mSockFd);
			free(pJsonData);
			responseStr = std::string("Failed to read size of receivable data");
			return -1;
	   } else if(size_of_data == 0) {
			shutdown(mSockFd, SHUT_RDWR);
			close (mSockFd);
			free(pJsonData);
			responseStr = std::string("server closed the connection");
			return -1;
		}
	}

	sprintf(log_msg, "size of receivable data is %d",  pJsonData->sizeofdata.nSize);
	LOG(debug, __FILE__, __LINE__, log_msg);
	pJsonData->pData = (char*) malloc(pJsonData->sizeofdata.nSize + 1);
	int nBytesRead = read_receivable_data(mSockFd, pJsonData);

	if(nBytesRead >= 0) {
		//pJsonData->nBytesRead += nBytesRead;
		if(pJsonData->nBytesRead - 2 >= pJsonData->sizeofdata.nSize) {
			pJsonData->pData[pJsonData->sizeofdata.nSize] = '\0';
			LOG(info, __FILE__, __LINE__, "Response received is:");
			LOG(info, __FILE__, __LINE__, pJsonData->pData);
		}
	}

	responseStr = pJsonData->pData;

	free(pJsonData->pData);
	free(pJsonData);

	return 0;
}

int ImaTcpRequestMaker::read_size_of_receivable_data(int fd, JSONDATA* pJsonData) {
	LOG(debug, __FILE__, __LINE__, "ImaTcpRequestMaker::read_size_of_receivable_data called");

	ssize_t received = 0;

	while(pJsonData->nBytesRead != 2) {
		received = read(fd, pJsonData->sizeofdata.buffer, 2-pJsonData->nBytesRead);
		if (received == -1) {
			/* If errno == EAGAIN, that means we have read all
			data. So go back to the main loop. */
			if (errno != EAGAIN) {
				LOG(error, __FILE__, __LINE__, "read failed");
				return -1;
			}
			break;
		} else if (received == 0) {
			/* End of file. The remote has closed the
			connection. */
			LOG(error, __FILE__, __LINE__, "The remote host closed the connection");
			return -1;
		} else {
			pJsonData->nBytesRead += received;
		}
	}

	if(pJsonData->nBytesRead > 2) {
		LOG(error, __FILE__, __LINE__, "Something went wrong while reading the size of receivable data");
		return -1;
	} else if(pJsonData->nBytesRead < 2) {
		char log_msg[1024];
		sprintf(log_msg, "Size of data to read is < 2");
		LOG(info, __FILE__, __LINE__, log_msg);
		return -1;
	}

	char log_msg[1024];
	sprintf(log_msg, "size of data to read is %d", pJsonData->nBytesRead);
	LOG(info, __FILE__, __LINE__, log_msg);
	return 2;
}

int ImaTcpRequestMaker::read_receivable_data(int fd, JSONDATA* pJsonData) {
	LOG(debug, __FILE__, __LINE__, "ImaTcpRequestMaker::read_receivable_data invoked");
	while (1) {
		ssize_t count = 0;
		char buf[512];
		count = read (fd, buf, sizeof(buf));
		if (count == -1) {
			/* If errno == EAGAIN, that means we have read all
			   data. So go back to the main loop. */
			if (errno != EAGAIN) {
				char log_msg[1024];
				sprintf(log_msg, "Error in read: %s", strerror(errno));
				LOG(error, __FILE__, __LINE__, log_msg);
				return -1;
			} else {
				return 0;
			}
		} else if (count == 0) {
			/* End of file. The remote has closed the connection. */
			char log_msg[1024];
			sprintf(log_msg, "Error in read: The remote host closed the connection; Error: %s", strerror(errno));
			LOG(error, __FILE__, __LINE__, log_msg);
			return -1;
		} else if(count > 0) {
			memcpy(pJsonData->pData + (pJsonData->nBytesRead - 2), buf, count);
			pJsonData->nBytesRead += count;
			if((pJsonData->nBytesRead - 2) == pJsonData->sizeofdata.nSize) {
				return pJsonData->nBytesRead - 2;
			} 
		}
	}

	return 0;
}
